package ferp.core.card;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * User: olegs
 * Date: 19/05/11 15:12
 */
public final class Card
{
  // card can appear face up, face down or hidden
  public static enum State {FACE_DOWN, FACE_UP, HIDDEN}

  // suits
  public static enum Suit
  {
    SPADES, CLUBS, DIAMONDS, HEARTS, NONE;

    // in rising order
    public static final Suit rising[] = {SPADES, CLUBS, DIAMONDS, HEARTS};
    public static final ArrayList<Suit> risingAsList = new ArrayList<Suit>(Suit.rising.length);

    // get next (higher) suit - can not be called for NONE
    public static Suit next(Suit suit) {return Suit.values()[suit.ordinal() + 1];}
    // get previous (lower) suit - can not be called for SPADES
    public static Suit previous(Suit suit) {return Suit.values()[suit.ordinal() - 1];}
    // get suit by short name
    public static Suit byShortName(String sn)
    {
      for (int i = 0; i < shortNames.length; ++i)
        if (sn.equals(shortNames[i]))
          return Suit.values()[i];

      return null;
    }

    // get suit power
    public int power() {return shifts[ordinal()];}

    @Override
    public String toString() {return symbols[ordinal()];}
    
    public String shortName() {return shortNames[ordinal()];}

    // shift table
    private static final int[] shifts = {24, 16, 8, 0};
    // suit symbols
    private static final String[] symbols = {"\u2660", "\u2663", "\u2666", "\u2665", "."};
    // suit short names
    private static final String[] shortNames = {"s", "c", "d", "h", "n"};

    static
    {
      risingAsList.addAll(Arrays.asList(rising));
    }
  }

  // ranks
  public static enum Rank
  {
    SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE;

    public static final Rank rising[] = {SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE};
    public static final Rank falling[] = {ACE, KING, QUEEN, JACK, TEN, NINE, EIGHT, SEVEN};

    // calculate rank 32 bit hash in lsb
    public int hash() {return r2h[ordinal()];}
    // calculate rank 32 bit hash, considering suit
    //public int hash(Suit suit) {return r2h[ordinal()] << suit.power();}

    @Override
    public String toString() {return titles[ordinal()];}

    // visible titles
    private static final String[] titles = {"7", "8", "9", "10", "J", "Q", "K", "A"};
    // rank to hash translation
    private static final int[] r2h = {1, 2, 4, 8, 16, 32, 64, 128};
    // hash to rank translation
    private static final Rank[] h2r = {
        null, SEVEN, EIGHT, null, NINE, null, null, null, // 0
        TEN, null, null, null, null, null, null, null,    // 8
        JACK, null, null, null, null, null, null, null,   // 16
        null, null, null, null, null, null, null, null,   // 24
        QUEEN, null, null, null, null, null, null, null,  // 32
        null, null, null, null, null, null, null, null,   // 40
        null, null, null, null, null, null, null, null,   // 48
        null, null, null, null, null, null, null, null,   // 56
        KING, null, null, null, null, null, null, null,   // 64
        null, null, null, null, null, null, null, null,   // 72
        null, null, null, null, null, null, null, null,   // 80
        null, null, null, null, null, null, null, null,   // 88
        null, null, null, null, null, null, null, null,   // 96
        null, null, null, null, null, null, null, null,   // 104
        null, null, null, null, null, null, null, null,   // 112
        null, null, null, null, null, null, null, null,   // 120
        ACE                                               // 128
    };

    // get rank object by its 8 bit hash in lsb
    private static Rank rank(int hash) {return h2r[hash];}
  }

  // card set (one suit)
  public static final class Set
  {
    // get number of cards in set
    public static int size(int hash) {return sizes[hash];}

    // get hash value containing only cards from the given suit moved to the LSB)
    public static int get(int hash, Card.Suit suit) {return (hash >>> suit.power()) & 0xFF;}

    // check if given rank exists in the hash value
    public static boolean contains(int hash, Rank rank) {return (hash & rank.hash()) != 0;}
    // check if given card hash exists in the set hash value
    public static boolean contains(int hash, int card) {return (hash & card) != 0;}

    // get the highest card in the set
    public static Rank highest(int hash) {return Rank.h2r[highest[hash]];}
    // get the lowest card in the set (LSB hash)
    public static Rank lowest(int hash) {return Rank.h2r[lowest[hash]];}
    // get the closest higher rank for the given rank in the hash value
    public static Rank higher(int hash, Rank rank) {return lowest(hash & higher[rank.ordinal()]);}
    // get the highest card in this set that lower than provided one
    public static Rank lower(int hash, Rank rank) {return highest(hash & lower[rank.ordinal()]);}

    public static void dump(StringBuilder sb, int hash)
    {
      boolean first = true;
      // print cards ranks in descending order
      for (Card.Rank r : Card.Rank.falling)
        if (contains(hash, r))
        {
          if (!first)
            sb.append(", ");
          else
            first = false;

          sb.append(r);
        }
    }

    // set size table
    private static final byte[] sizes = {
      /* 00000000 */ (byte)0,
      /* 00000001 */ (byte)1,
      /* 00000010 */ (byte)1,
      /* 00000011 */ (byte)2,
      /* 00000100 */ (byte)1,
      /* 00000101 */ (byte)2,
      /* 00000110 */ (byte)2,
      /* 00000111 */ (byte)3,
      /* 00001000 */ (byte)1,
      /* 00001001 */ (byte)2,
      /* 00001010 */ (byte)2,
      /* 00001011 */ (byte)3,
      /* 00001100 */ (byte)2,
      /* 00001101 */ (byte)3,
      /* 00001110 */ (byte)3,
      /* 00001111 */ (byte)4,
      /* 00010000 */ (byte)1,
      /* 00010001 */ (byte)2,
      /* 00010010 */ (byte)2,
      /* 00010011 */ (byte)3,
      /* 00010100 */ (byte)2,
      /* 00010101 */ (byte)3,
      /* 00010110 */ (byte)3,
      /* 00010111 */ (byte)4,
      /* 00011000 */ (byte)2,
      /* 00011001 */ (byte)3,
      /* 00011010 */ (byte)3,
      /* 00011011 */ (byte)4,
      /* 00011100 */ (byte)3,
      /* 00011101 */ (byte)4,
      /* 00011110 */ (byte)4,
      /* 00011111 */ (byte)5,
      /* 00100000 */ (byte)1,
      /* 00100001 */ (byte)2,
      /* 00100010 */ (byte)2,
      /* 00100011 */ (byte)3,
      /* 00100100 */ (byte)2,
      /* 00100101 */ (byte)3,
      /* 00100110 */ (byte)3,
      /* 00100111 */ (byte)4,
      /* 00101000 */ (byte)2,
      /* 00101001 */ (byte)3,
      /* 00101010 */ (byte)3,
      /* 00101011 */ (byte)4,
      /* 00101100 */ (byte)3,
      /* 00101101 */ (byte)4,
      /* 00101110 */ (byte)4,
      /* 00101111 */ (byte)5,
      /* 00110000 */ (byte)2,
      /* 00110001 */ (byte)3,
      /* 00110010 */ (byte)3,
      /* 00110011 */ (byte)4,
      /* 00110100 */ (byte)3,
      /* 00110101 */ (byte)4,
      /* 00110110 */ (byte)4,
      /* 00110111 */ (byte)5,
      /* 00111000 */ (byte)3,
      /* 00111001 */ (byte)4,
      /* 00111010 */ (byte)4,
      /* 00111011 */ (byte)5,
      /* 00111100 */ (byte)4,
      /* 00111101 */ (byte)5,
      /* 00111110 */ (byte)5,
      /* 00111111 */ (byte)6,
      /* 01000000 */ (byte)1,
      /* 01000001 */ (byte)2,
      /* 01000010 */ (byte)2,
      /* 01000011 */ (byte)3,
      /* 01000100 */ (byte)2,
      /* 01000101 */ (byte)3,
      /* 01000110 */ (byte)3,
      /* 01000111 */ (byte)4,
      /* 01001000 */ (byte)2,
      /* 01001001 */ (byte)3,
      /* 01001010 */ (byte)3,
      /* 01001011 */ (byte)4,
      /* 01001100 */ (byte)3,
      /* 01001101 */ (byte)4,
      /* 01001110 */ (byte)4,
      /* 01001111 */ (byte)5,
      /* 01010000 */ (byte)2,
      /* 01010001 */ (byte)3,
      /* 01010010 */ (byte)3,
      /* 01010011 */ (byte)4,
      /* 01010100 */ (byte)3,
      /* 01010101 */ (byte)4,
      /* 01010110 */ (byte)4,
      /* 01010111 */ (byte)5,
      /* 01011000 */ (byte)3,
      /* 01011001 */ (byte)4,
      /* 01011010 */ (byte)4,
      /* 01011011 */ (byte)5,
      /* 01011100 */ (byte)4,
      /* 01011101 */ (byte)5,
      /* 01011110 */ (byte)5,
      /* 01011111 */ (byte)6,
      /* 01100000 */ (byte)2,
      /* 01100001 */ (byte)3,
      /* 01100010 */ (byte)3,
      /* 01100011 */ (byte)4,
      /* 01100100 */ (byte)3,
      /* 01100101 */ (byte)4,
      /* 01100110 */ (byte)4,
      /* 01100111 */ (byte)5,
      /* 01101000 */ (byte)3,
      /* 01101001 */ (byte)4,
      /* 01101010 */ (byte)4,
      /* 01101011 */ (byte)5,
      /* 01101100 */ (byte)4,
      /* 01101101 */ (byte)5,
      /* 01101110 */ (byte)5,
      /* 01101111 */ (byte)6,
      /* 01110000 */ (byte)3,
      /* 01110001 */ (byte)4,
      /* 01110010 */ (byte)4,
      /* 01110011 */ (byte)5,
      /* 01110100 */ (byte)4,
      /* 01110101 */ (byte)5,
      /* 01110110 */ (byte)5,
      /* 01110111 */ (byte)6,
      /* 01111000 */ (byte)4,
      /* 01111001 */ (byte)5,
      /* 01111010 */ (byte)5,
      /* 01111011 */ (byte)6,
      /* 01111100 */ (byte)5,
      /* 01111101 */ (byte)6,
      /* 01111110 */ (byte)6,
      /* 01111111 */ (byte)7,
      /* 10000000 */ (byte)1,
      /* 10000001 */ (byte)2,
      /* 10000010 */ (byte)2,
      /* 10000011 */ (byte)3,
      /* 10000100 */ (byte)2,
      /* 10000101 */ (byte)3,
      /* 10000110 */ (byte)3,
      /* 10000111 */ (byte)4,
      /* 10001000 */ (byte)2,
      /* 10001001 */ (byte)3,
      /* 10001010 */ (byte)3,
      /* 10001011 */ (byte)4,
      /* 10001100 */ (byte)3,
      /* 10001101 */ (byte)4,
      /* 10001110 */ (byte)4,
      /* 10001111 */ (byte)5,
      /* 10010000 */ (byte)2,
      /* 10010001 */ (byte)3,
      /* 10010010 */ (byte)3,
      /* 10010011 */ (byte)4,
      /* 10010100 */ (byte)3,
      /* 10010101 */ (byte)4,
      /* 10010110 */ (byte)4,
      /* 10010111 */ (byte)5,
      /* 10011000 */ (byte)3,
      /* 10011001 */ (byte)4,
      /* 10011010 */ (byte)4,
      /* 10011011 */ (byte)5,
      /* 10011100 */ (byte)4,
      /* 10011101 */ (byte)5,
      /* 10011110 */ (byte)5,
      /* 10011111 */ (byte)6,
      /* 10100000 */ (byte)2,
      /* 10100001 */ (byte)3,
      /* 10100010 */ (byte)3,
      /* 10100011 */ (byte)4,
      /* 10100100 */ (byte)3,
      /* 10100101 */ (byte)4,
      /* 10100110 */ (byte)4,
      /* 10100111 */ (byte)5,
      /* 10101000 */ (byte)3,
      /* 10101001 */ (byte)4,
      /* 10101010 */ (byte)4,
      /* 10101011 */ (byte)5,
      /* 10101100 */ (byte)4,
      /* 10101101 */ (byte)5,
      /* 10101110 */ (byte)5,
      /* 10101111 */ (byte)6,
      /* 10110000 */ (byte)3,
      /* 10110001 */ (byte)4,
      /* 10110010 */ (byte)4,
      /* 10110011 */ (byte)5,
      /* 10110100 */ (byte)4,
      /* 10110101 */ (byte)5,
      /* 10110110 */ (byte)5,
      /* 10110111 */ (byte)6,
      /* 10111000 */ (byte)4,
      /* 10111001 */ (byte)5,
      /* 10111010 */ (byte)5,
      /* 10111011 */ (byte)6,
      /* 10111100 */ (byte)5,
      /* 10111101 */ (byte)6,
      /* 10111110 */ (byte)6,
      /* 10111111 */ (byte)7,
      /* 11000000 */ (byte)2,
      /* 11000001 */ (byte)3,
      /* 11000010 */ (byte)3,
      /* 11000011 */ (byte)4,
      /* 11000100 */ (byte)3,
      /* 11000101 */ (byte)4,
      /* 11000110 */ (byte)4,
      /* 11000111 */ (byte)5,
      /* 11001000 */ (byte)3,
      /* 11001001 */ (byte)4,
      /* 11001010 */ (byte)4,
      /* 11001011 */ (byte)5,
      /* 11001100 */ (byte)4,
      /* 11001101 */ (byte)5,
      /* 11001110 */ (byte)5,
      /* 11001111 */ (byte)6,
      /* 11010000 */ (byte)3,
      /* 11010001 */ (byte)4,
      /* 11010010 */ (byte)4,
      /* 11010011 */ (byte)5,
      /* 11010100 */ (byte)4,
      /* 11010101 */ (byte)5,
      /* 11010110 */ (byte)5,
      /* 11010111 */ (byte)6,
      /* 11011000 */ (byte)4,
      /* 11011001 */ (byte)5,
      /* 11011010 */ (byte)5,
      /* 11011011 */ (byte)6,
      /* 11011100 */ (byte)5,
      /* 11011101 */ (byte)6,
      /* 11011110 */ (byte)6,
      /* 11011111 */ (byte)7,
      /* 11100000 */ (byte)3,
      /* 11100001 */ (byte)4,
      /* 11100010 */ (byte)4,
      /* 11100011 */ (byte)5,
      /* 11100100 */ (byte)4,
      /* 11100101 */ (byte)5,
      /* 11100110 */ (byte)5,
      /* 11100111 */ (byte)6,
      /* 11101000 */ (byte)4,
      /* 11101001 */ (byte)5,
      /* 11101010 */ (byte)5,
      /* 11101011 */ (byte)6,
      /* 11101100 */ (byte)5,
      /* 11101101 */ (byte)6,
      /* 11101110 */ (byte)6,
      /* 11101111 */ (byte)7,
      /* 11110000 */ (byte)4,
      /* 11110001 */ (byte)5,
      /* 11110010 */ (byte)5,
      /* 11110011 */ (byte)6,
      /* 11110100 */ (byte)5,
      /* 11110101 */ (byte)6,
      /* 11110110 */ (byte)6,
      /* 11110111 */ (byte)7,
      /* 11111000 */ (byte)5,
      /* 11111001 */ (byte)6,
      /* 11111010 */ (byte)6,
      /* 11111011 */ (byte)7,
      /* 11111100 */ (byte)6,
      /* 11111101 */ (byte)7,
      /* 11111110 */ (byte)7,
      /* 11111111 */ (byte)8
    };

    // highest lookup table
    private static final int[] highest = {
        0, 1, 2, 2, 4, 4, 4, 4, 8, 8,
        8, 8, 8, 8, 8, 8, 16, 16, 16, 16,
        16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
        16, 16, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
        32, 32, 32, 32, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
        128, 128, 128, 128, 128, 128};

    // lowest lookup table
    private static final int[] lowest = {
        0, 1, 2, 1, 4, 1, 2, 1, 8, 1,
        2, 1, 4, 1, 2, 1, 16, 1, 2, 1,
        4, 1, 2, 1, 8, 1, 2, 1, 4, 1,
        2, 1, 32, 1, 2, 1, 4, 1, 2, 1,
        8, 1, 2, 1, 4, 1, 2, 1, 16, 1,
        2, 1, 4, 1, 2, 1, 8, 1, 2, 1,
        4, 1, 2, 1, 64, 1, 2, 1, 4, 1,
        2, 1, 8, 1, 2, 1, 4, 1, 2, 1,
        16, 1, 2, 1, 4, 1, 2, 1, 8, 1,
        2, 1, 4, 1, 2, 1, 32, 1, 2, 1,
        4, 1, 2, 1, 8, 1, 2, 1, 4, 1,
        2, 1, 16, 1, 2, 1, 4, 1, 2, 1,
        8, 1, 2, 1, 4, 1, 2, 1, 128, 1,
        2, 1, 4, 1, 2, 1, 8, 1, 2, 1,
        4, 1, 2, 1, 16, 1, 2, 1, 4, 1,
        2, 1, 8, 1, 2, 1, 4, 1, 2, 1,
        32, 1, 2, 1, 4, 1, 2, 1, 8, 1,
        2, 1, 4, 1, 2, 1, 16, 1, 2, 1,
        4, 1, 2, 1, 8, 1, 2, 1, 4, 1,
        2, 1, 64, 1, 2, 1, 4, 1, 2, 1,
        8, 1, 2, 1, 4, 1, 2, 1, 16, 1,
        2, 1, 4, 1, 2, 1, 8, 1, 2, 1,
        4, 1, 2, 1, 32, 1, 2, 1, 4, 1,
        2, 1, 8, 1, 2, 1, 4, 1, 2, 1,
        16, 1, 2, 1, 4, 1, 2, 1, 8, 1,
        2, 1, 4, 1, 2, 1};

    // higher mask table
    private static final int[] higher = {0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 0x00};

    // lower mask table
    private static final int[] lower = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F};
  }

  // card hash masks
  public static final class Mask
  {
    // single cards
    public static final int ACE = Rank.ACE.hash();
    public static final int KING = Rank.KING.hash();
    public static final int QUEEN = Rank.QUEEN.hash();
    public static final int JACK = Rank.JACK.hash();
    public static final int EIGHT = Rank.EIGHT.hash();
    public static final int SEVEN = Rank.SEVEN.hash();

    // card combinations
    public static final int C_78 = SEVEN | EIGHT;
  }

  // check if valid index
  public static boolean isValidIndex(byte compressed) {return compressed > 0 && compressed < 33;}

  public static Card card(Suit s, Rank r) {return table[s.ordinal()][r.ordinal()];}
  public static Card card(byte index) {return objects[index];}
  // get card object given its 32-bit hash
  public static Card card(int hash)
  {
    int low16 = hash & 0xFFFF, high16 = (hash >>> 16); // & 0xFFFF;
    
    if (low16 != 0)
      return hash2card(low16, Suit.HEARTS, Suit.DIAMONDS);

    if (high16 != 0)
      return hash2card(high16, Suit.CLUBS, Suit.SPADES);

    // should never get here
    return null;
  }

  // card data
  public final Suit suit;
  public final Rank rank;
  // 32-bit hash value
  public final int hash;
  // 6 bit index
  public final byte compressed;

  @Override
  public int hashCode() {return hash;}
  @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
  @Override
  public boolean equals(Object o) {return this == o;}

  @Override
  public String toString() {return suit.toString() + rank.toString();}

  private static Card hash2card(int hash, Suit lower, Suit higher)
  {
    int low8 = hash & 0xFF, high8 = (hash >>> 8); // & 0xFF;
    
    if (low8 != 0)
      return card(lower, Rank.rank(low8));

    if (high8 != 0)
      return card(higher, Rank.rank(high8));

    // should never get here
    return null;
  }

  // card objects
  private static final Card[] objects = new Card[Card.Suit.rising.length * Card.Rank.values().length + 1];
  // lookup table for card objects by suit and rank
  private static final Card[][] table = new Card[Card.Suit.rising.length][Card.Rank.values().length];

  private Card(Suit suit, Rank rank, byte index)
  {
    this.suit = suit;
    this.rank = rank;
    this.hash = rank.hash() << suit.power();
    this.compressed = index;
  }

/*
  public static void main(String[] argv)
  {
*/
/*
    for (int i = 0; i < 256; ++i)
    {
      System.out.println(Hand.dump(i) + ": highest=" + Set.highest(i) + ", lowest=" + Set.lowest(i));

      for (Rank r : Rank.values())
        System.out.println("  Rank=" + r + ", higher=" + Set.higher(i, r) + ", lower=" + Set.lower(i, r));
    }
*//*


*/
/*
    System.out.println("// highest lookup table");
    System.out.println("private static final int[] highest = {");

    for (int i = 0; i < 256; ++i)
    {
      if (i != 0 && i % 10 == 0)
        System.out.println();

      Rank r = Card.Set.highest(i);

      System.out.print(r == null ? "0, " : Rank.r2h[r.ordinal()] + ", ");
    }

    System.out.println("};");

    System.out.println();

    System.out.println("// lowest lookup table");
    System.out.println("private static final int[] lowest = {");

    for (int i = 0; i < 256; ++i)
    {
      if (i != 0 && i % 10 == 0)
        System.out.println();

      Rank r = Card.Set.lowest(i);

      System.out.print(r == null ? "0, " : Rank.r2h[r.ordinal()] + ", ");
    }

    System.out.println("};");
*//*

  }
*/

  static
  {
    byte i = 1;

    for (Suit s : Suit.rising)
      for (Rank r : Rank.rising)
        table[s.ordinal()][r.ordinal()] = objects[i] = new Card(s, r, i++);

    // 0 is invalid index value
    objects[0] = null;
 }
}
